/*
 ****************************************************************
 * 版权所有@2015 国裕网络科技  保留所有权利.
 ***************************************************************/

package com.linln.admin.system.aop;

import com.linln.admin.system.task.CommonTask;
import com.linln.common.enums.ResultEnum;
import com.linln.common.utils.StringUtils;
import com.linln.common.vo.ResultVo;
import com.linln.modules.system.domain.SysLog;
import org.slf4j.MDC;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import java.lang.reflect.Method;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 用于记录用户操作日志
 */
@Aspect
@Component
@EnableAsync
public class SysLogAop {

    private static final Logger logger = LoggerFactory.getLogger(SysLogAop.class);

    @Autowired
    private CommonTask commonTask;


    //匹配该包及其子包下的所有类的所有方法
    @Pointcut("execution(* com.linln.admin.system.controller..*.*(..))")
    public void controllerPointCut(){

    }


    /**
     * 前置通知
     * @param joinPoint
     */
//    @Before("controllerPointCut()  ")
//    public void doBeforeAdvice(JoinPoint joinPoint){
//        //获取目标方法的参数信息
//        Object[] obj = joinPoint.getArgs();
//        //AOP代理类的信息
//        joinPoint.getThis();
//        //代理的目标对象
//        joinPoint.getTarget();
//        //用的最多 通知的签名
//        Signature signature = joinPoint.getSignature();
    //代理的是哪一个方法
    //System.out.println( signature.getName());
    //AOP代理类的名字
//        logger.info("前置通知:" + signature.getDeclaringTypeName() + "." + signature.getName() + "(),参数：" + Arrays.asList(obj));
//        //AOP代理类的类（class）信息
//        signature.getDeclaringType();
//        //获取RequestAttributes
//        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
//        //从获取RequestAttributes中获取HttpServletRequest的信息
//        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
//        //如果要获取Session信息的话，可以这样写：
//        //HttpSession session = (HttpSession) requestAttributes.resolveReference(RequestAttributes.REFERENCE_SESSION);
//        Enumeration<String> enumeration = request.getParameterNames();
//        Map<String,String> parameterMap = new HashMap<String,String>();
//        while (enumeration.hasMoreElements()){
//            String parameter = enumeration.nextElement();
//            parameterMap.put(parameter,request.getParameter(parameter));
//        }
//        String str = JSON.toJSONString(parameterMap);
//        if(obj.length > 0) {
//            System.out.println("请求的参数信息为："+str);
//        }
//    }
    /**
     * 环绕通知：
     *   环绕通知非常强大，可以决定目标方法是否执行，什么时候执行，执行时是否需要替换方法参数，执行完毕是否需要替换返回值。
     *   环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
     */
    @Around("controllerPointCut()")
    public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
        //获取controller功能名称(前提是需要在 RequestMapping注解中配置好name属性)
        Signature signature = proceedingJoinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature)signature;
        Class returnTypeClazz = methodSignature.getReturnType();//方法的返回值类型

        Method targetMethod = methodSignature.getMethod();//目标方法
        Class targetClazz = null ;//目标类
        try {
            targetClazz = proceedingJoinPoint.getTarget().getClass();
            targetMethod = proceedingJoinPoint.getTarget().getClass().getDeclaredMethod(signature.getName(), targetMethod.getParameterTypes());
        } catch (NoSuchMethodException e) {
            logger.debug(targetClazz + "没有" + proceedingJoinPoint.getSignature().getName() + "()方法，请查看其继承的类");
//            proceedingJoinPoint.getTarget().getClass();
        }

        StringBuffer functionName = new StringBuffer();//业务名称
        //获取类名上的注解
        if(targetClazz != null && targetClazz.isAnnotationPresent(RequestMapping.class)) {
            RequestMapping requestMapping = (RequestMapping) targetClazz.getAnnotation(RequestMapping.class);
            functionName.append(requestMapping.name()) ;
        }
        if(!StringUtils.isEmpty(functionName.toString())){
            functionName.append("--");
        }

        //获取方法上的注解
        if(targetMethod.isAnnotationPresent(RequestMapping.class)) {
            RequestMapping requestMapping = targetMethod.getAnnotation(RequestMapping.class);
            functionName.append(requestMapping.name());
        }else if(targetMethod.isAnnotationPresent(GetMapping.class)){
            GetMapping getMapping = targetMethod.getAnnotation(GetMapping.class);
            functionName.append(getMapping.name());
        }else if(targetMethod.isAnnotationPresent(PostMapping.class)){
            PostMapping postMapping = targetMethod.getAnnotation(PostMapping.class);
            functionName.append(postMapping.name());
        }else if(targetMethod.isAnnotationPresent(PutMapping.class)){
            PutMapping putMapping = targetMethod.getAnnotation(PutMapping.class);
            functionName.append(putMapping.name());
        }else if(targetMethod.isAnnotationPresent(DeleteMapping.class)){
            DeleteMapping deleteMapping = targetMethod.getAnnotation(DeleteMapping.class);
            functionName.append(deleteMapping.name());
        }

        String userName = MDC.get("username");//当前登录的用户名
        String ipAddress = MDC.get("address");//当前登录用户的ip地址
        boolean executeResult = false;//方法执行结果：成功 or 失败
        String executeResultMsg = "";//方法执行结果信息
        Object obj = null;
        String usedTimeStr = "";
        long usedTime = 0L;
        try {
            long begin = System.currentTimeMillis();
            obj = proceedingJoinPoint.proceed();
            long end = System.currentTimeMillis();
            usedTime = end - begin;
            usedTimeStr = (usedTime > 1000 ? (new Double(Long.toString(usedTime))/1000D) +" 秒" : usedTime + " 毫秒");

            if(null == obj && "void".equals(returnTypeClazz.getName())){
                executeResult = true;
            }else if(null != obj && obj instanceof String){
                executeResult = true;
            }else if(null != obj && obj instanceof ModelAndView){
                executeResult = true;
            }else if(null != obj && obj instanceof ResultVo){
                Integer code = ((ResultVo) obj).getCode();
                executeResult = ResultEnum.SUCCESS.getCode().equals(code);
                executeResultMsg = ((ResultVo) obj).getMsg();
            }else if(null != obj && obj instanceof List){
                executeResult = true;
            }else if(null != obj && obj instanceof Map){
                executeResult = true;

            }else if(null != obj && obj instanceof ResponseEntity){
                /* 文件上传 */
//                Object body = ((ResponseEntity) obj).getBody();
//                if(body instanceof List){
//                    List list = (List)body;
//                    if(!list.isEmpty() && null != list.get(0)){
//                        Object element = list.get(0);
//                        if(element instanceof  UploadInfo){
//                            UploadInfo uploadInfo = (UploadInfo) element;
//                            UploadCode uploadCode = uploadInfo.getCode();
//                            executeResult = uploadCode == UploadCode.SUCCESS;
//                        }
//                    }
//                }
            }
        } catch (Throwable throwable) {
            logger.error("执行" + functionName + "功能时发生异常:" + throwable.getMessage(),throwable);
            throw new RuntimeException("执行" + functionName + "功能时发生异常:" + throwable.getMessage());
        }

        logger.debug(functionName + "【" + proceedingJoinPoint.getTarget().getClass() + "." + proceedingJoinPoint.getSignature().getName() + "()执行完毕】耗时 : " + usedTimeStr +
                ",执行结果：【" + (executeResult ? "成功" : "失败") + (StringUtils.isNotEmpty(executeResultMsg) ? ",msg:" + executeResultMsg : "")  +"】 ,请求参数：" + handleParams(proceedingJoinPoint));


        SysLog logs = null;
        logs = new SysLog();
        logs.setOperatorType(2);//0其它，1管理员，2用户
        logs.setOperatorName(StringUtils.isNotEmpty(userName) ? userName : "system");
        logs.setBusinessName(functionName.toString());
        logs.setBusinessRemark(executeResultMsg);
        logs.setBusinessStatus(executeResult ? 1 : 0);
        logs.setOperatorIp(StringUtils.isNotEmpty(ipAddress) ? ipAddress : "127.0.0.1");
        logs.setCreateTime(new Timestamp(System.currentTimeMillis()));
        logs.setCreator(StringUtils.isNotEmpty(userName) ? userName : "system");
        logs.setTimeCount((int) usedTime);
        logs.setTimeCountDesc(usedTimeStr);
        Object requestParamsObject = handleParams(proceedingJoinPoint);
        logs.setRequestParams(requestParamsObject == null ? "" : requestParamsObject.toString());
        logs.setClassInfo(proceedingJoinPoint.getTarget().getClass() + "." + proceedingJoinPoint.getSignature().getName() + "()");
        commonTask.log(logs);//异步保存日志
        return obj;
    }

    /**
     * 后置最终通知（目标方法只要执行完了就会执行后置通知方法）
     * @param joinPoint
     */
    @After("controllerPointCut()")
    public void doAfterAdvice(JoinPoint joinPoint){
        logger.debug("后置通知执行了!!!!");
    }


    /**
     * 后置异常通知
     *  定义一个名字，该名字用于匹配通知实现方法的一个参数名，当目标方法抛出异常返回后，将把目标方法抛出的异常传给通知方法；
     *  throwing 限定了只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知，否则不执行，
     *      对于throwing对应的通知方法参数为Throwable类型将匹配任何异常。
     * @param joinPoint
     * @param exception
     */
    @AfterThrowing(value = "controllerPointCut()",throwing = "exception")
    public void doAfterThrowingAdvice(JoinPoint joinPoint,Throwable exception){
        //目标方法名：
        logger.debug(joinPoint.getSignature().getName());
        if(exception instanceof NullPointerException){
            logger.debug("发生了空指针异常!!!!!");
        }
    }

    /**
     * 返回通知
     * 这里需要注意的是AfterReturning有个方法重载:
     *      如果参数中的第一个参数为JoinPoint，则第二个参数为返回值的信息
     *      如果参数中的第一个参数不为JoinPoint，则第一个参数为returning中对应的参数
     * returning 限定了只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知，否则不执行，对于returning对应的通知方法参数为Object类型将匹配任何目标返回值
     * @param joinPoint
     * @param returnValue
     */
    @AfterReturning(value = "controllerPointCut()",returning = "returnValue")
    public void doAfterReturningAdvice(JoinPoint joinPoint,Object returnValue){
        logger.debug("返回通知的返回值：" + returnValue);
    }


    /**
     * 记录请求参数
     * 只记录基本类型、本项目相关的类型，框架中的类型不做记录
     * @param proceedingJoinPoint
     * @return
     */
    private List handleParams(ProceedingJoinPoint proceedingJoinPoint){
        List list = new ArrayList<>();
        if(null == proceedingJoinPoint){
            return null;
        }
        for(Object obj : proceedingJoinPoint.getArgs()){
            if(null == obj) {
                continue;
            }
            if(obj.getClass().getName().contains("com.linln") || obj.getClass().getName().contains("java.lang")
                    || obj.getClass().getName().contains("java.util")){
                list.add(obj);
                continue;
            }
        }
        return list;
    }
    /**
     * 记录请求参数
     * 只记录基本类型、本项目相关的类型，框架中的类型不做记录
     * @param proceedingJoinPoint
     * @return
     */
    private List handleParamsLength(ProceedingJoinPoint proceedingJoinPoint){
        List list = new ArrayList<>();
        if(null == proceedingJoinPoint){
            return null;
        }
        for(Object obj : proceedingJoinPoint.getArgs()){
            if(null == obj) {
                continue;
            }
            if(obj.getClass().getName().contains("com.linln") || obj.getClass().getName().contains("java.lang")
                    || obj.getClass().getName().contains("java.util")){
                list.add(obj);
                continue;
            }
            String str  = obj.toString();
            if(StringUtils.isNotEmpty(str) && str.length() > 1000){ //太长的数据不放到数据库中,可以查看log文件
                str = str.substring(0,1000);
                list.add(str);
                continue;
            }
        }
        return list;
    }

}
